/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: png.c,v 1.4 2005/07/12 23:12:29 pullmoll Exp $
 *****************************************************************************/
#include "osd.h"
#include "config.h"
#include "memalloc.h"
#include "tools.h"
#include "png.h"
#include "logger.h"

static uint8_t pngmagic[] = {137, 80, 78, 71, 13, 10, 26, 10};

static uint32_t isocrc[] = {
		0x00000000L,0x77073096L,0xee0e612cL,0x990951baL,
		0x076dc419L,0x706af48fL,0xe963a535L,0x9e6495a3L,
		0x0edb8832L,0x79dcb8a4L,0xe0d5e91eL,0x97d2d988L,
		0x09b64c2bL,0x7eb17cbdL,0xe7b82d07L,0x90bf1d91L,
		0x1db71064L,0x6ab020f2L,0xf3b97148L,0x84be41deL,
		0x1adad47dL,0x6ddde4ebL,0xf4d4b551L,0x83d385c7L,
		0x136c9856L,0x646ba8c0L,0xfd62f97aL,0x8a65c9ecL,
		0x14015c4fL,0x63066cd9L,0xfa0f3d63L,0x8d080df5L,
		0x3b6e20c8L,0x4c69105eL,0xd56041e4L,0xa2677172L,
		0x3c03e4d1L,0x4b04d447L,0xd20d85fdL,0xa50ab56bL,
		0x35b5a8faL,0x42b2986cL,0xdbbbc9d6L,0xacbcf940L,
		0x32d86ce3L,0x45df5c75L,0xdcd60dcfL,0xabd13d59L,
		0x26d930acL,0x51de003aL,0xc8d75180L,0xbfd06116L,
		0x21b4f4b5L,0x56b3c423L,0xcfba9599L,0xb8bda50fL,
		0x2802b89eL,0x5f058808L,0xc60cd9b2L,0xb10be924L,
		0x2f6f7c87L,0x58684c11L,0xc1611dabL,0xb6662d3dL,
		0x76dc4190L,0x01db7106L,0x98d220bcL,0xefd5102aL,
		0x71b18589L,0x06b6b51fL,0x9fbfe4a5L,0xe8b8d433L,
		0x7807c9a2L,0x0f00f934L,0x9609a88eL,0xe10e9818L,
		0x7f6a0dbbL,0x086d3d2dL,0x91646c97L,0xe6635c01L,
		0x6b6b51f4L,0x1c6c6162L,0x856530d8L,0xf262004eL,
		0x6c0695edL,0x1b01a57bL,0x8208f4c1L,0xf50fc457L,
		0x65b0d9c6L,0x12b7e950L,0x8bbeb8eaL,0xfcb9887cL,
		0x62dd1ddfL,0x15da2d49L,0x8cd37cf3L,0xfbd44c65L,
		0x4db26158L,0x3ab551ceL,0xa3bc0074L,0xd4bb30e2L,
		0x4adfa541L,0x3dd895d7L,0xa4d1c46dL,0xd3d6f4fbL,
		0x4369e96aL,0x346ed9fcL,0xad678846L,0xda60b8d0L,
		0x44042d73L,0x33031de5L,0xaa0a4c5fL,0xdd0d7cc9L,
		0x5005713cL,0x270241aaL,0xbe0b1010L,0xc90c2086L,
		0x5768b525L,0x206f85b3L,0xb966d409L,0xce61e49fL,
		0x5edef90eL,0x29d9c998L,0xb0d09822L,0xc7d7a8b4L,
		0x59b33d17L,0x2eb40d81L,0xb7bd5c3bL,0xc0ba6cadL,
		0xedb88320L,0x9abfb3b6L,0x03b6e20cL,0x74b1d29aL,
		0xead54739L,0x9dd277afL,0x04db2615L,0x73dc1683L,
		0xe3630b12L,0x94643b84L,0x0d6d6a3eL,0x7a6a5aa8L,
		0xe40ecf0bL,0x9309ff9dL,0x0a00ae27L,0x7d079eb1L,
		0xf00f9344L,0x8708a3d2L,0x1e01f268L,0x6906c2feL,
		0xf762575dL,0x806567cbL,0x196c3671L,0x6e6b06e7L,
		0xfed41b76L,0x89d32be0L,0x10da7a5aL,0x67dd4accL,
		0xf9b9df6fL,0x8ebeeff9L,0x17b7be43L,0x60b08ed5L,
		0xd6d6a3e8L,0xa1d1937eL,0x38d8c2c4L,0x4fdff252L,
		0xd1bb67f1L,0xa6bc5767L,0x3fb506ddL,0x48b2364bL,
		0xd80d2bdaL,0xaf0a1b4cL,0x36034af6L,0x41047a60L,
		0xdf60efc3L,0xa867df55L,0x316e8eefL,0x4669be79L,
		0xcb61b38cL,0xbc66831aL,0x256fd2a0L,0x5268e236L,
		0xcc0c7795L,0xbb0b4703L,0x220216b9L,0x5505262fL,
		0xc5ba3bbeL,0xb2bd0b28L,0x2bb45a92L,0x5cb36a04L,
		0xc2d7ffa7L,0xb5d0cf31L,0x2cd99e8bL,0x5bdeae1dL,
		0x9b64c2b0L,0xec63f226L,0x756aa39cL,0x026d930aL,
		0x9c0906a9L,0xeb0e363fL,0x72076785L,0x05005713L,
		0x95bf4a82L,0xe2b87a14L,0x7bb12baeL,0x0cb61b38L,
		0x92d28e9bL,0xe5d5be0dL,0x7cdcefb7L,0x0bdbdf21L,
		0x86d3d2d4L,0xf1d4e242L,0x68ddb3f8L,0x1fda836eL,
		0x81be16cdL,0xf6b9265bL,0x6fb077e1L,0x18b74777L,
		0x88085ae6L,0xff0f6a70L,0x66063bcaL,0x11010b5cL,
		0x8f659effL,0xf862ae69L,0x616bffd3L,0x166ccf45L,
		0xa00ae278L,0xd70dd2eeL,0x4e048354L,0x3903b3c2L,
		0xa7672661L,0xd06016f7L,0x4969474dL,0x3e6e77dbL,
		0xaed16a4aL,0xd9d65adcL,0x40df0b66L,0x37d83bf0L,
		0xa9bcae53L,0xdebb9ec5L,0x47b2cf7fL,0x30b5ffe9L,
		0xbdbdf21cL,0xcabac28aL,0x53b39330L,0x24b4a3a6L,
		0xbad03605L,0xcdd70693L,0x54de5729L,0x23d967bfL,
		0xb3667a2eL,0xc4614ab8L,0x5d681b02L,0x2a6f2b94L,
		0xb40bbe37L,0xc30c8ea1L,0x5a05df1bL,0x2d02ef8dL
};

#define	FONTNAME	"node/font8x13.txt"
static uint8_t *font8x13 = NULL;

void isocrc_reset(uint32_t *pcrc)
{
	*pcrc = 0xffffffff;
}

void isocrc_byte(uint32_t *pcrc, uint8_t b)
{
	uint32_t crc = *pcrc;
	*pcrc = isocrc[(uint8_t)(*pcrc ^ b)] ^ (crc >> 8);
}

void isocrc_bytes(uint32_t *pcrc, uint8_t *buff, size_t size)
{
	size_t i;
	uint32_t crc = *pcrc;
	for (i = 0; i < size; i++)
		crc = isocrc[(uint8_t)(crc ^ buff[i])] ^ (crc >> 8);
	*pcrc = crc;
}

void isocrc_string(uint32_t *pcrc, const char *src)
{
	uint32_t crc = *pcrc;
	while (*src) {
		crc = isocrc[(uint8_t)(crc ^ (uint8_t)*src)] ^ (crc >> 8);
		src++;
	}
	*pcrc = crc;
}

int png_write_size(png_t *png, uint32_t size)
{
	FUN("png_write_size");
	LOG(L_MINOR,("png_write_size(%p,%#x)\n",
		png, (unsigned)size));

	if (NULL == png || NULL == png->output) {
		errno = EINVAL;
		return -1;
	}
	if (0 != (*png->output)(png->user, (uint8_t)(size >> 24)))
		return -1;
	if (0 != (*png->output)(png->user, (uint8_t)(size >> 16)))
		return -1;
	if (0 != (*png->output)(png->user, (uint8_t)(size >> 8)))
		return -1;
	if (0 != (*png->output)(png->user, (uint8_t)(size)))
		return -1;
	isocrc_reset(&png->crc);
	return 0;
}

int png_read_size(png_t *png, uint32_t *psize)
{
	uint32_t size;
	int i;
	FUN("png_read_size");

	LOG(L_MINOR,("png_read_size(%p,%p)\n",
		png, psize));

	if (NULL == png || NULL == png->input || NULL == psize) {
		errno = EINVAL;
		return -1;
	}

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	size = ((uint32_t)i) << 24;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	size |= ((uint32_t)i) << 16;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	size |= ((uint32_t)i) << 8;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	size |= ((uint32_t)i);

	*psize = size;

	isocrc_reset(&png->crc);
	return 0;
}

int png_write_uint(png_t *png, uint32_t ui)
{
	FUN("png_write_uint");

	LOG(L_MINOR,("png_write_uint(%p,%#x)\n",
		png, (unsigned)ui));

	if (NULL == png || NULL == png->output) {
		errno = EINVAL;
		return -1;
	}
	isocrc_byte(&png->crc, (uint8_t)(ui >> 24));
	if (0 != (*png->output)(png->user, (uint8_t)(ui >> 24)))
		return -1;

	isocrc_byte(&png->crc, (uint8_t)(ui >> 16));
	if (0 != (*png->output)(png->user, (uint8_t)(ui >> 16)))
		return -1;

	isocrc_byte(&png->crc, (uint8_t)(ui >> 8));
	if (0 != (*png->output)(png->user, (uint8_t)(ui >> 8)))
		return -1;

	isocrc_byte(&png->crc, (uint8_t)(ui));
	if (0 != (*png->output)(png->user, (uint8_t)(ui)))
		return -1;

	return 0;
}

int png_read_uint(png_t *png, uint32_t *puint)
{
	uint32_t uint;
	int i;
	FUN("png_read_uint");

	LOG(L_MINOR,("png_read_uint(%p,%p)\n",
		png, puint));
	if (NULL == png || NULL == png->input || NULL == puint) {
		errno = EINVAL;
		return -1;
	}
	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	isocrc_byte(&png->crc, (uint8_t)i);
	uint = ((uint32_t)i) << 24;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	isocrc_byte(&png->crc, (uint8_t)i);
	uint |= ((uint32_t)i) << 16;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	isocrc_byte(&png->crc, (uint8_t)i);
	uint |= ((uint32_t)i) << 8;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	isocrc_byte(&png->crc, (uint8_t)i);
	uint |= ((uint32_t)i);

	*puint = uint;

	return 0;
}

int png_write_string(png_t *png, const char *src)
{
	FUN("png_write_string");

	LOG(L_MINOR,("png_write_string(%p,'%s')\n",
		png, src));
	if (NULL == png || NULL == png->output) {
		errno = EINVAL;
		return -1;
	}
	isocrc_string(&png->crc, src);
	while (*src) {
		if (0 != (*png->output)(png->user, (uint8_t)*src))
			return -1;
		src++;
	}
	return 0;
}

int png_read_string(png_t *png, char *dst, uint32_t size)
{
	uint32_t offs;
	int i;
	FUN("png_read_string");

	LOG(L_MINOR,("png_read_string(%p,%p,%#x)\n",
		png, dst, (unsigned)size));

	if (NULL == png || NULL == png->input || NULL == dst || 0 == size) {
		errno = EINVAL;
		return -1;
	}

	for (offs = 0; offs < size; offs++) {
		if (-1 == (i = (*png->input)(png->user)))
			return -1;
		dst[offs] = (char)i;
	}
	dst[offs] = '\0';

	isocrc_string(&png->crc, dst);

	return 0;
}

int png_write_bytes(png_t *png, uint8_t *buff, uint32_t size)
{
	uint32_t offs;
	FUN("png_write_bytes");

	LOG(L_MINOR,("png_write_bytes(%p,%p,%#x)\n",
		png, buff, (int)size));

	if (NULL == png || NULL == png->output) {
		errno = EINVAL;
		return -1;
	}

	isocrc_bytes(&png->crc, buff, size);
	for (offs = 0; offs < size; offs++)
		if (0 != (*png->output)(png->user, buff[offs]))
			return -1;

	return 0;
}

int png_read_bytes(png_t *png, uint8_t *buff, uint32_t size)
{
	uint32_t offs;
	int i;
	FUN("png_read_bytes");

	LOG(L_MINOR,("png_read_bytes(%p,%p,%#x)\n",
		png, buff, (unsigned)size));

	if (NULL == png || NULL == png->input || NULL == buff || 0 == size) {
		errno = EINVAL;
		return -1;
	}

	for (offs = 0; offs < size; offs++) {
		if (-1 == (i = (*png->input)(png->user)))
			return -1;
		buff[offs] = (uint8_t)i;
	}

	isocrc_bytes(&png->crc, buff, size);

	return 0;
}

int png_write_byte(png_t *png, uint8_t b)
{
	FUN("png_write_byte");

	LOG(L_MINOR,("png_write_byte(%p,%u)\n",
		png, b));

	if (NULL == png || NULL == png->output) {
		errno = EINVAL;
		return -1;
	}

	isocrc_byte(&png->crc, b);

	if (0 != (*png->output)(png->user, b))
		return -1;

	return 0;
}

int png_read_byte(png_t *png, uint8_t *pbyte)
{
	int i;
	FUN("png_read_byte");

	LOG(L_MINOR,("png_read_byte(%p,%p)\n",
		png, pbyte));

	if (NULL == png || NULL == png->input || NULL == pbyte) {
		errno = EINVAL;
		return -1;
	}

	if (-1 == (i = (*png->input)(png->user)))
		return -1;

	isocrc_byte(&png->crc, (uint8_t)i);

	*pbyte = (uint8_t)i;

	return 0;
}

int png_write_crc(png_t *png)
{
	uint32_t crc;
	FUN("png_write_crc");

	LOG(L_MINOR,("png_write_crc(%p) -> %#x\n",
		png, png->crc));

	if (NULL == png || NULL == png->output) {
		errno = EINVAL;
		return -1;
	}

	crc = ~png->crc;

	if (0 != (*png->output)(png->user, (uint8_t)(crc >> 24)))
		return -1;

	if (0 != (*png->output)(png->user, (uint8_t)(crc >> 16)))
		return -1;

	if (0 != (*png->output)(png->user, (uint8_t)(crc >> 8)))
		return -1;

	if (0 != (*png->output)(png->user, (uint8_t)(crc)))
		return -1;

	return 0;
}

int png_read_crc(png_t *png)
{
	uint32_t crc;
	int i;
	FUN("png_read_crc");

	LOG(L_MINOR,("png_read_crc(%p)\n",
		png));

	if (NULL == png || NULL == png->input) {
		errno = EINVAL;
		return -1;
	}

	png->crc = ~png->crc;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	crc = ((uint32_t)i) << 24;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	crc |= ((uint32_t)i) << 16;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	crc |= ((uint32_t)i) << 8;

	if (-1 == (i = (*png->input)(png->user)))
		return -1;
	crc |= ((uint32_t)i);

	LOG(L_DEBUG,("got CRC %08x, expect CRC %08x\n",
		crc, png->crc));
	if (png->crc != crc) {
		LOG(L_ERROR,("CRC mismatch\n"));
		return -1;
	}

	return 0;
}

int png_write_IHDR(png_t *png)
{
	int rc;
	FUN("png_write_IHDR");
	LOG(L_MINOR,("png_write_IHDR(%p)\n",
		png));

	if (0 != (rc = png_write_size(png,13)))			/* 13 bytes */
		return rc;
	if (0 != (rc = png_write_string(png,"IHDR")))	/* image header */
		return rc;
	if (0 != (rc = png_write_uint(png,png->w)))
		return rc;
	if (0 != (rc = png_write_uint(png,png->h)))
		return rc;
	if (0 != (rc = png_write_byte(png,png->depth)))
		return rc;
	if (0 != (rc = png_write_byte(png,png->color)))
		return rc;
	if (0 != (rc = png_write_byte(png,png->compression)))
		return rc;
	if (0 != (rc = png_write_byte(png,png->filter)))
		return rc;
	if (0 != (rc = png_write_byte(png,png->interlace)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_IHDR(png_t *png, uint32_t size)
{
	uint32_t part;
	int rc;
	FUN("png_read_IHDR");
	LOG(L_MINOR,("png_read_IHDR(%p,%#x)\n",
		png, (unsigned)size));

	/* already got size and 'IHDR' tag */
	part = 13;
	if (size < part) {
		LOG(L_ERROR,("size is less than %u\n", part));
		errno = EINVAL;
		return -1;
	}
	if (0 != (rc = png_read_uint(png,&png->w))) {
		LOG(L_ERROR,("failed to read width\n"));
		return rc;
	}
	if (0 != (rc = png_read_uint(png,&png->h))) {
		LOG(L_ERROR,("failed to read height\n"));
		return rc;
	}
	if (0 != (rc = png_read_byte(png,&png->depth))) {
		LOG(L_ERROR,("failed to read depth\n"));
		return rc;
	}
	if (0 != (rc = png_read_byte(png,&png->color))) {
		LOG(L_ERROR,("failed to read color mode\n"));
		return rc;
	}
	if (0 != (rc = png_read_byte(png,&png->compression))) {
		LOG(L_ERROR,("failed to read compression mode\n"));
		return rc;
	}
	if (0 != (rc = png_read_byte(png,&png->filter))) {
		LOG(L_ERROR,("failed to read filter mode\n"));
		return rc;
	}
	if (0 != (rc = png_read_byte(png,&png->interlace))) {
		LOG(L_ERROR,("failed to read interlace mode\n"));
		return rc;
	}
	if (size > part) {
		uint32_t i;
		LOG(L_DEBUG,("skipping %u bytes\n", size - part));
		for (i = part; i < size; i++) {
			uint8_t ignore;
			if (0 != (rc = png_read_byte(png,&ignore)))
				return rc;
		}
	}
	if (0 != (rc = png_read_crc(png))) {
		LOG(L_ERROR,("CRC check failed\n"));
		return rc;
	}
	return 0;
}

int png_write_pHYs(png_t *png)
{
	size_t size = sizeof(png->px) + sizeof(png->py) + sizeof(png->unit);
	int rc;
	FUN("png_write_pHYs");
	LOG(L_MINOR,("png_write_pHYs(%p)\n",
		png));

	if (0 != (rc = png_write_size(png, size)))	
		return rc;
	if (0 != (rc = png_write_string(png, "pHYs")))
		return rc;
	if (0 != (rc = png_write_uint(png, png->px)))
		return rc;
	if (0 != (rc = png_write_uint(png, png->py)))
		return rc;
	if (0 != (rc = png_write_byte(png, png->unit)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_pHYs(png_t *png, uint32_t size)
{
	uint32_t part;
	int rc;
	FUN("png_read_pHYs");
	LOG(L_MINOR,("png_read_pHYs(%p,%#x)\n",
		png, (unsigned)size));

	part = 9;
	if (size < part) {
		LOG(L_ERROR,("size is less than %u\n", part));
		errno = EINVAL;
		return -1;
	}
	/* already got size and 'pHYs' tag */
	if (0 != (rc = png_read_uint(png, &png->px)))
		return rc;
	if (0 != (rc = png_read_uint(png, &png->py)))
		return rc;
	if (0 != (rc = png_read_byte(png, &png->unit)))
		return rc;
	if (size > part) {
		uint32_t i;
		LOG(L_DEBUG,("skipping %u bytes\n", size - part));
		for (i = part; i < size; i++) {
			uint8_t ignore;
			if (0 != (rc = png_read_byte(png,&ignore)))
				return rc;
		}
	}
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_write_tIME(png_t *png)
{
	uint32_t size = sizeof(png->time);
	int rc;
	FUN("png_write_tIME");
	LOG(L_MINOR,("png_write_tIME(%p)\n",
		png));

	if (0 != (rc = png_write_size(png, size)))
		return rc;
	if (0 != (rc = png_write_string(png, "tIME")))
		return rc;
	if (0 != (rc = png_write_bytes(png, png->time, size)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_tIME(png_t *png, uint32_t size)
{
	uint32_t part = sizeof(png->time);
	int rc;
	FUN("png_read_tIME");
	LOG(L_MINOR,("png_read_tIME(%p,%#x)\n",
		png, (unsigned)size));

	/* already got size and 'tIME' tag */
	if (size < part) {
		LOG(L_ERROR,("size is less than %u\n", part));
		errno = EINVAL;
		return -1;
	}
	if (0 != (rc = png_read_bytes(png, png->time, part)))
		return rc;
	if (size > part) {
		uint32_t i;
		LOG(L_DEBUG,("skipping %u bytes\n", size - part));
		for (i = part; i < size; i++) {
			uint8_t ignore;
			if (0 != (rc = png_read_byte(png,&ignore)))
				return rc;
		}
	}
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_write_tEXt(png_t *png, const char *src)
{
	uint32_t size = strlen(src);
	int rc;
	FUN("png_write_tEXt");
	LOG(L_MINOR,("png_write_tEXt(%p,'%s')\n",
		png, src));

	if (0 != (rc = png_write_size(png, size)))	
		return rc;
	if (0 != (rc = png_write_string(png, "tEXt")))
		return rc;
	if (0 != (rc = png_write_string(png, src)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_tEXt(png_t *png, char *dst, uint32_t size)
{
	int rc;
	FUN("png_read_tEXt");
	LOG(L_MINOR,("png_read_tEXt(%p,%p,%#x)\n",
		png, dst, (unsigned)size));

	/* already got size and 'tEXt' tag */
	if (0 != (rc = png_read_string(png, dst, size)))
		return rc;
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_write_PLTE(png_t *png)
{
	uint32_t entries = 1 << png->bpp;
	uint32_t size = 3 * entries;
	int rc;
	FUN("png_write_PLTE");
	LOG(L_MINOR,("png_write_PLTE(%p) -> %u entries\n",
		png, (unsigned)entries));

	if (0 != (rc = png_write_size(png, size)))	
		return rc;
	if (0 != (rc = png_write_string(png, "PLTE")))
		return rc;
	if (0 != (rc = png_write_bytes(png, png->pal, size)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_PLTE(png_t *png, uint32_t size)
{
	uint32_t part = sizeof(png->pal);
	uint32_t entries = 1 << png->bpp;
	int rc;
	FUN("png_read_PLTE");
	LOG(L_MINOR,("png_read_PLTE(%p) -> max %u entries\n",
		png, (unsigned)entries));

	if (entries > size / 3) {
		LOG(L_MINOR,("read only %u of %u palette entries\n",
			size / 3, entries));
		entries = size / 3;
	}
	if (3 * entries < part) {
		part = 3 * entries;
	}

	/* already got size and 'PLTE' tag */
	if (0 != (rc = png_read_bytes(png, png->pal, part)))
		return rc;
	if (size > part) {
		uint32_t i;
		LOG(L_DEBUG,("skipping %u bytes\n", size - part));
		for (i = part; i < size; i++) {
			uint8_t ignore;
			if (0 != (rc = png_read_byte(png,&ignore)))
				return rc;
		}
	}
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_write_tRNS(png_t *png)
{
	uint32_t size = png->trns_size;
	int rc;
	FUN("png_write_tRNS");

	LOG(L_MINOR,("png_write_tRNS(%p) -> %u bytes\n",
		png, (unsigned)size));
	if (0 != (rc = png_write_size(png, size)))	
		return rc;
	if (0 != (rc = png_write_string(png, "tRNS")))
		return rc;
	if (0 != (rc = png_write_bytes(png, png->trns, size)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_tRNS(png_t *png, uint32_t size)
{
	uint32_t part = png->trns_size;
	int rc;
	FUN("png_read_tRNS");

	LOG(L_MINOR,("png_read_tRNS(%p) -> %u bytes\n",
		png, (unsigned)part));
	if (part > size) {
		part = size;
	}
	if (0 != (rc = png_read_bytes(png, png->trns, part)))
		return rc;
	if (size > part) {
		uint32_t i;
		LOG(L_DEBUG,("skipping %u bytes\n", size - part));
		for (i = part; i < size; i++) {
			uint8_t ignore;
			if (0 != (rc = png_read_byte(png,&ignore)))
				return rc;
		}
	}
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_write_bKGD(png_t *png)
{
	int rc;
	FUN("png_write_bKGD");
	LOG(L_MINOR,("png_write_bKGD(%p)\n",
		png));

	if (0 != (rc = png_write_size(png,png->bkgd_size)))	
		return rc;
	if (0 != (rc = png_write_string(png,"bKGD")))	/* background header */
		return rc;
	if (0 != (rc = png_write_bytes(png,png->bkgd,png->bkgd_size)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_bKGD(png_t *png, uint32_t size)
{
	int rc;
	uint32_t i;
	FUN("png_read_bKGD");
	LOG(L_MINOR,("png_read_bKGD(%p,%#x)\n",
		png, (unsigned)size));

	png->bkgd_size = (size > sizeof(png->bkgd)) ? sizeof(png->bkgd) : size;

	/* already got size and 'bKGD' tag */
	if (0 != (rc = png_read_bytes(png, png->bkgd, png->bkgd_size)))
		return rc;
	for (i = png->bkgd_size; i < size; i++) {
		uint8_t ignore;
		if (0 != (rc = png_read_byte(png,&ignore)))
			return rc;
	}
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_write_IDAT(png_t *png, uint8_t *idat, uint32_t size)
{
	int rc;
	FUN("png_write_IDAT");
	LOG(L_MINOR,("png_write_IDAT(%p,%p,%#x)\n",
		png, idat, (unsigned)size));

	if (0 != (rc = png_write_size(png,size)))	
		return rc;
	if (0 != (rc = png_write_string(png,"IDAT")))	/* image data */
		return rc;
	if (0 != (rc = png_write_bytes(png,idat,size)))
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_IDAT(png_t *png, uint8_t *idat, uint32_t size)
{
	int rc;
	FUN("png_read_IDAT");
	LOG(L_MINOR,("png_read_IDAT(%p,%p,%#x)\n",
		png, idat, (unsigned)size));

	/* already got size and 'IDAT' tag */
	if (0 != (rc = png_read_bytes(png,idat,size)))
		return rc;
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_write_IEND(png_t *png)
{
	int rc;
	FUN("png_write_IEND");
	LOG(L_MINOR,("png_write_IEND(%p)\n",
		png));

	if (0 != (rc = png_write_size(png,0)))	
		return rc;
	if (0 != (rc = png_write_string(png,"IEND")))	/* end of PNG */
		return rc;
	if (0 != (rc = png_write_crc(png)))	
		return rc;
	return 0;
}

int png_read_IEND(png_t *png, uint32_t size)
{
	uint32_t i;
	int rc;
	FUN("png_read_IEND");
	LOG(L_MINOR,("png_read_IEND(%p,%#x)\n",
		png, (unsigned)size));

	/* already got size and 'IEND' tag */
	for (i = 0; i < size; i++) {
		uint8_t ignore;
		if (0 != (rc = png_read_byte(png,&ignore)))
			return rc;
	}
	if (0 != (rc = png_read_crc(png)))	
		return rc;
	return 0;
}

int png_finish(png_t *png)
{
	uint8_t *idat;
	uLong gzsize;
	uint32_t i;
	int rc;
	FUN("png_finish");
	LOG(L_MINOR,("png_finish(%p)\n",
		png));

	if (NULL == png->output)
		return -EINVAL;
	/* write PNG magic */
	for (i = 0; i < sizeof(pngmagic); i++)
		(*png->output)(png->user, pngmagic[i]);

	if (0 != (rc = png_write_IHDR(png)))
		return rc;
	if (NULL != png->comment) {
		if (0 != (rc = png_write_tEXt(png,png->comment)))
			return rc;
	}
	if (NULL != png->author) {
		if (0 != (rc = png_write_tEXt(png,png->author)))
			return rc;
	}
	if (png->color == COLOR_PALETTE) {
		if (0 != (rc = png_write_PLTE(png)))
			return rc;
		if (0 != (rc = png_write_bKGD(png)))
			return rc;
	}
	if (0 != png->px && 0 != png->py) {
		if (0 != (rc = png_write_pHYs(png)))
			return rc;
	}

	gzsize = (png->row + 1) * png->h * 1001 / 1000 + 12;
	idat = xmalloc(gzsize);
	if (Z_OK != (rc = compress(idat, &gzsize, png->img, png->size)))
		return rc;
	if (0 != (rc = png_write_IDAT(png,idat,gzsize)))
		return rc;
	if (0 != (rc = png_write_IEND(png)))
		return rc;

	xfree(idat);
	xfree(png->img);
	xfree(png);
	return 0;
}

int png_read_fd(void *user)
{
	uint8_t b;
	int fd = *(int *)user;
	if (1 == read(fd, &b, 1))
		return b;
	return -1;
}

png_t *png_read(const char *filename,
	void *user, int (*output)(void *user, uint8_t data))
{
	png_t *png = NULL;
	char tag[4+1];
	uint32_t size;
	uint32_t i, x, y, sub, up;
	int iend, fd, rc;
	FUN("png_read");
	LOG(L_MINOR,("png_read('%s')\n",
		filename));

	fd = open(filename, O_RDONLY|O_BINARY);
	if (-1 == fd) {
		LOG(L_ERROR,("open('%s',O_RDONLY|O_BINARY) call failed (%s)\n",
			filename, strerror(errno)));
		return NULL;
	}
	png = (png_t *)xcalloc(1, sizeof(png_t));

	png->user = &fd;
	png->input = png_read_fd;

	/* read PNG magic */
	for (i = 0; i < sizeof(pngmagic); i++) {
		if (pngmagic[i] != (*png->input)(png->user)) {
			LOG(L_ERROR,("PNG magic #%d failed\n", i));
			xfree(png);
			close(fd);
			return NULL;
		}
	}

	iend = 0;
	while (0 == iend) {
		if (0 != (rc = png_read_size(png, &size))) {
			LOG(L_ERROR,("PNG read size failed (%s)\n",
				strerror(errno)));
			goto bailout;
		}
		if (0 != (rc = png_read_string(png, tag, 4))) {
			LOG(L_ERROR,("PNG read tag failed (%s)\n",
				strerror(errno)));
			goto bailout;
		}
		LOG(L_DEBUG,("PNG '%s' size %#x\n", tag, size));
		if (0 == strcmp(tag, "IHDR")) {
			if (0 != (rc = png_read_IHDR(png, size))) {
				LOG(L_ERROR,("PNG read IHDR failed (%s)\n",
					strerror(errno)));
				goto bailout;
			}
			switch (png->color) {
			case COLOR_GRAYSCALE:
				switch (png->depth) {
				case 1:
					png->row = (png->w + 7) / 8 + 1;
					png->bpp = 1;
					png->trns_size = 2;
					LOG(L_DEBUG,("PNG COLOR_GRAYSCALE 1bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 2:
					png->row = (png->w + 3) / 4 + 1;
					png->bpp = 2;
					png->trns_size = 2;
					LOG(L_DEBUG,("PNG COLOR_GRAYSCALE 2bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 4:
					png->row = (png->w + 1) / 2 + 1;
					png->bpp = 4;
					png->trns_size = 2;
					LOG(L_DEBUG,("PNG COLOR_GRAYSCALE 4bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 8:
					png->row = png->w + 1;
					png->bpp = 8;
					png->trns_size = 2;
					LOG(L_DEBUG,("PNG COLOR_GRAYSCALE 8bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 16:
					png->row = 2 * png->w + 1;
					png->bpp = 16;
					png->trns_size = 2;
					LOG(L_DEBUG,("PNG COLOR_GRAYSCALE 16bpp: %#x\n",
						(unsigned)png->row));
					break;
				default:
					LOG(L_ERROR,("unsupported COLOR_GRAYSCALE depth %d\n",
						(int)png->depth));
					rc = -1;
					goto bailout;
				}
				break;
			case COLOR_RGBTRIPLE:
				switch (png->depth) {
				case 8:
					png->row = 3 * png->w + 1;
					png->bpp = 3 * 8;
					png->trns_size = 6;
					LOG(L_DEBUG,("PNG COLOR_RGBTRIPLE 8bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 16:
					png->row = 6 * png->w + 1;
					png->bpp = 3 * 16;
					png->trns_size = 6;
					LOG(L_DEBUG,("PNG COLOR_RGBTRIPLE 16bpp: %#x\n",
						(unsigned)png->row));
					break;
				default:
					LOG(L_ERROR,("unsupported COLOR_RGBTRIPLE depth %d\n",
						(int)png->depth));
					rc = -1;
					goto bailout;
				}
				break;
			case COLOR_PALETTE:
				switch (png->depth) {
				case 1:
					png->row = png->w + 1;
					png->bpp = 1;
					png->trns_size = 2;
					LOG(L_DEBUG,("PNG COLOR_PALETTE 1bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 2:
					png->row = png->w + 1;
					png->bpp = 2;
					png->trns_size = 4;
					LOG(L_DEBUG,("PNG COLOR_PALETTE 2bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 4:
					png->row = png->w + 1;
					png->bpp = 4;
					png->trns_size = 16;
					LOG(L_DEBUG,("PNG COLOR_PALETTE 4bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 8:
					png->row = png->w + 1;
					png->bpp = 8;
					png->trns_size = 256;
					LOG(L_DEBUG,("PNG COLOR_PALETTE 8bpp: %#x\n",
						(unsigned)png->row));
					break;
				default:
					LOG(L_ERROR,("unsupported COLOR_PALETTE depth %d\n",
						(int)png->depth));
					rc = -1;
					goto bailout;
				}
				break;
			case COLOR_GRAYALPHA:
				switch (png->depth) {
				case 8:
					png->row = 2 * png->w + 1;
					png->bpp = 2 * 8;
					png->trns_size = 0;
					LOG(L_DEBUG,("PNG COLOR_GRAYALPHA 8bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 16:
					png->row = 4 * png->w + 1;
					png->bpp = 2 * 16;
					png->trns_size = 0;
					LOG(L_DEBUG,("PNG COLOR_GRAYALPHA 16bpp: %#x\n",
						(unsigned)png->row));
					break;
				default:
					LOG(L_ERROR,("unsupported COLOR_GRAYALPHA depth %d\n",
						(int)png->depth));
					rc = -1;
					goto bailout;
				}
				break;
			case COLOR_RGBALPHA:
				switch (png->depth) {
				case 8:
					png->row = 4 * png->w + 1;
					png->bpp = 4 * 8;
					png->trns_size = 0;
					LOG(L_DEBUG,("PNG COLOR_RGBALPHA 8bpp: %#x\n",
						(unsigned)png->row));
					break;
				case 16:
					png->row = 2 * 4 * png->w + 1;
					png->bpp = 4 * 16;
					png->trns_size = 0;
					LOG(L_DEBUG,("PNG COLOR_RGBALPHA 16bpp: %#x\n",
						(unsigned)png->row));
					break;
				default:
					LOG(L_ERROR,("unsupported COLOR_RGBALPHA depth %d\n",
						(int)png->depth));
					rc = -1;
					goto bailout;
				}
				break;
			default:
				LOG(L_ERROR,("unsupported color model %d, depth %d\n",
					png->color, png->depth));
				rc = -1;
				goto bailout;
			}
			png->size = png->h * png->row;
			LOG(L_DEBUG,("PNG image size is %#x, %ux%ux%u, %u bytes/row\n",
				(unsigned)png->size, png->w, png->h, png->bpp, (unsigned)png->row));
			png->img = xcalloc(png->size, sizeof(uint8_t));
		} else if (0 == strcmp(tag, "tEXt")) {
			char *tmp = xcalloc(size + 1, sizeof(char));
			if (0 != (rc = png_read_tEXt(png, tmp, size))) {
				LOG(L_ERROR,("PNG read 'tEXt' failed (%s)\n",
					strerror(errno)));
				goto bailout;
			}
			LOG(L_MINOR,("tEXt: '%s'\n", tmp));
			xfree(tmp);
		} else if (0 == strcmp(tag, "PLTE")) {
			if (0 != (rc = png_read_PLTE(png, size))) {
				LOG(L_ERROR,("PNG read 'PLTE' failed (%s)\n",
					strerror(errno)));
				goto bailout;
			}
		} else if (0 == strcmp(tag, "bKGD")) {
			if (0 != (rc = png_read_bKGD(png, size))) {
				LOG(L_ERROR,("PNG read 'bKGD' failed (%s)\n",
					strerror(errno)));
				goto bailout;
			}
		} else if (0 == strcmp(tag, "pHYs")) {
			if (0 != (rc = png_read_pHYs(png, size))) {
				LOG(L_ERROR,("PNG read 'pHYs' failed (%s)\n",
					strerror(errno)));
				goto bailout;
			}
		} else if (0 == strcmp(tag, "IDAT")) {
			uint8_t *idat = xmalloc(size);
			uint8_t *scan = &png->img[png->offs];
			uLong gzsize = png->size;

			if (0 != (rc = png_read_IDAT(png,idat,size))) {
				LOG(L_ERROR,("PNG read 'IDAT' failed (%s)\n",
					strerror(errno)));
				goto bailout;
			}
			if (Z_OK != (rc = uncompress(scan, &gzsize, idat, size))) {
				LOG(L_ERROR,("uncompress(%p,%#x,%p,%#x) call failed (%d)\n",
					png->img, (unsigned)gzsize,
					idat, (unsigned)size, rc));
				xfree(idat);
				goto bailout;
			}
			png->offs += gzsize;
			xfree(idat);
		} else if (0 == strcmp(tag, "IEND")) {
			if (0 != (rc = png_read_IEND(png, size))) {
				LOG(L_ERROR,("PNG read IEND failed (%s)\n",
					strerror(errno)));
				goto bailout;
			}
			iend = 1;
		} else {
			LOG(L_MINOR,("PNG ignore tag '%s'\n", tag));
			for (i = 0; i < size; i++) {
				uint8_t ignore;
				png_read_byte(png, &ignore);
			}
			if (0 != (rc = png_read_crc(png))) {
				LOG(L_ERROR,("PNG crc of unknown tag '%s' failed\n",
					tag));
				goto bailout;
			}
		}
	}
	close(fd);
	fd = -1;

	/* undo the filters */
	for (y = 0; y < (uint32_t)png->h; y++) {
		uint8_t *row = png->img + png->row * y;
		switch (row[0]) {
		case 0:
			/* no filter */
			break;
		case 1:
			/* sub filter */
			sub = png->bpp / 8;
			for (x = 0; x < png->row - 1; x++) {
				uint32_t a;
				/* left */
				a = x < sub ? 0 : row[1+x - sub];
				row[1+x] += a;
			}
			row[0] = 0;
			break;
		case 2:
			/* up filter */
			up = png->row;
			for (x = 0; x < png->row - 1; x++) {
				uint32_t b;
				/* above */
				b = y < 1 ? 0 : row[1+x - up];
				row[1+x] += b;
			}
			row[0] = 0;
			break;
		case 3:
			/* average filter */
			sub = png->bpp / 8;
			up = png->row;
			for (x = 0; x < png->row - 1; x++) {
				uint32_t a, b;
				/* left */
				a = x < sub ? 0 : row[1+x - sub];
				/* above */
				b = y < 1 ? 0 : row[1+x - up];
				row[1+x] += (a + b) / 2;
			}
			row[0] = 0;
			break;
		case 4:
			/* paeth filter */
			sub = png->bpp / 8;
			up = png->row;
			for (x = 0; x < png->row - 1; x++) {
				uint32_t a, b, c, p, pa, pb, pc;
				/* left */
				a = x < sub ? 0 : row[1+x - sub];
				/* above */
				b = y < 1 ? 0 : row[1+x - up];
				/* upper, left */
				c = x < sub || y < 1 ? 0 : row[1+x - sub - up];
				p = a + b - c;
				pa = (p >= a) ? p - a : a - p;
				pb = (p >= b) ? p - b : b - p;
				pc = (p >= c) ? p - c : c - p;
				if (pa <= pb && pa <= pc) {
					row[1+x] += a;
				} else if (pb <= pc) {
					row[1+x] += b;
				} else {
					row[1+x] += c;
				}
			}
			row[0] = 0;
			break;
		}
	}

	/* set the output user data and function */
	png->user = user;
	png->output = output;
	rc = 0;
bailout:
	if (-1 != fd) {
		close(fd);
		fd = -1;
	}
	if (0 != rc) {
		xfree(png->img);
		xfree(png);
	}
	return png;
}

png_t *png_create(int w, int h, int color, int depth,
	void *user, int (*output)(void *user, uint8_t data))
{
	png_t *png;
	FUN("png_create");
	LOG(L_MINOR,("png_create(%d,%d,%d,%d,%p,%p)\n",
		w, h, color, depth, user, output));

	png = (png_t *)xcalloc(1, sizeof(png_t));
	png->w = w;
	png->h = h;
	png->color = color;
	png->depth = depth;
	switch (color) {
	case COLOR_GRAYSCALE:
		switch (depth) {
		case 1:
			png->row = (png->w + 7) / 8 + 1;
			png->bkgd_size = 2;
			png->trns_size = 2;
			break;
		case 2:
			png->row = (png->w + 3) / 4 + 1;
			png->bkgd_size = 2;
			png->trns_size = 2;
			break;
		case 4:
			png->row = (png->w + 1) / 2 + 1;
			png->bkgd_size = 2;
			png->trns_size = 2;
			break;
		case 8:
			png->row = png->w + 1;
			png->bkgd_size = 2;
			png->trns_size = 2;
			break;
		case 16:
			png->row = 2 * png->w + 1;
			png->bkgd_size = 2;
			png->trns_size = 2;
			break;
		default:
			xfree(png);
			return NULL;
		}
		break;
	case COLOR_RGBTRIPLE:
		switch (depth) {
		case 8:
			png->row = 3 * png->w + 1;
			png->bkgd_size = 6;
			png->trns_size = 6;
			break;
		case 16:
			png->row = 6 * png->w + 1;
			png->bkgd_size = 6;
			png->trns_size = 6;
			break;
		default:
			xfree(png);
			return NULL;
		}
		break;
	case COLOR_PALETTE:
		switch (depth) {
		case 1:
			png->row = png->w + 1;
			png->bpp = 1;
			png->bkgd_size = 1;
			png->trns_size = 2;
			break;
		case 2:
			png->row = png->w + 1;
			png->bpp = 2;
			png->bkgd_size = 1;
			png->trns_size = 4;
			break;
		case 4:
			png->row = png->w + 1;
			png->bpp = 4;
			png->bkgd_size = 1;
			png->trns_size = 16;
			break;
		case 8:
			png->row = png->w + 1;
			png->bpp = 8;
			png->bkgd_size = 1;
			png->trns_size = 256;
			break;
		default:
			xfree(png);
			return NULL;
		}
		break;
	case COLOR_GRAYALPHA:
		switch (depth) {
		case 8:
			png->row = 2 * png->w + 1;
			png->bkgd_size = 2;
			png->trns_size = 0;
			break;
		case 16:
			png->row = 4 * png->w + 1;
			png->bkgd_size = 2;
			png->trns_size = 0;
			break;
		default:
			xfree(png);
			return NULL;
		}
		break;
	case COLOR_RGBALPHA:
		switch (depth) {
		case 8:
			png->row = 4 * png->w + 1;
			png->bkgd_size = 6;
			png->trns_size = 0;
			break;
		case 16:
			png->row = 8 * png->w + 1;
			png->bkgd_size = 6;
			png->trns_size = 0;
			break;
		default:
			xfree(png);
			return NULL;
		}
		break;
	default:
		xfree(png);
		return NULL;
	}
	png->size = png->h * png->row;
	png->img = xcalloc(png->size, sizeof(uint8_t));
	png->user = user;
	png->output = output;

	return png;
}

int png_set_palette(png_t *png, int idx, int color)
{
	uint32_t entry;

	if (idx < 0 || idx >= 256)
		return -EINVAL;
	entry = 3 * idx;
	png->pal[entry+0] = color >> 16;	/* red */
	png->pal[entry+1] = color >> 8;		/* green */
	png->pal[entry+2] = color;			/* blue */

	return 0;
}

int png_put_pixel(png_t *png, int x, int y, int color, int alpha)
{
	size_t offs;
	uint8_t mask;

	if (NULL == png || NULL == png->img) {
		errno = EINVAL;
		return -1;
	}
	if (x < 0 || x >= png->w || y < 0 || y >= png->h)
		return 0;

	if (-1 == color) {
		switch (png->bkgd_size) {
		case 1:
			color = png->bkgd[0];
			break;
		case 2:
			color = (png->bkgd[0] << 8) | png->bkgd[1];
			break;
		case 3:
			color = (png->bkgd[0] << 16) | (png->bkgd[1] << 8) | png->bkgd[2];
			break;
		case 6:
			color = (png->bkgd[0] << 16) | (png->bkgd[2] << 8) | png->bkgd[4];
			break;
		default:
			color = png->bkgd[0];
		}
	}

	switch (png->color) {
	case COLOR_GRAYSCALE:
		switch (png->depth) {
		case 1:
			offs = y * png->row + 1 + x / 8;
			mask = 0x80 >> (x % 8);
			if (color)
				png->img[offs] |= mask;
			else
				png->img[offs] &= ~mask;
			break;
		case 2:
			offs = y * png->row + 1 + x / 4;
			mask = 0xc0 >> (x % 4);
			if (color)
				png->img[offs] |= mask;
			else
				png->img[offs] &= ~mask;
			break;
		case 4:
			offs = y * png->row + 1 + x / 2;
			mask = 0xf0 >> (x % 2);
			if (color)
				png->img[offs] |= mask;
			else
				png->img[offs] &= ~mask;
			break;
		case 8:
			offs = y * png->row + 1 + x;
			png->img[offs] = color;
			break;
		case 16:
			offs = y * png->row + 1 + x * 2;
			png->img[offs+0] = color >> 8;
			png->img[offs+1] = color;
			break;
		}
		break;
	case COLOR_RGBTRIPLE:
		switch (png->depth) {
		case 8:
			offs = y * png->row + 1 + x * 3;
			png->img[offs+0] = color >> 16;
			png->img[offs+1] = color >> 8;
			png->img[offs+2] = color;
			break;
		case 16:
			offs = y * png->row + 1 + x * 6;
			png->img[offs+0] = color >> 16;
			png->img[offs+1] = 0xff;
			png->img[offs+2] = color >> 8;
			png->img[offs+3] = 0xff;
			png->img[offs+4] = color;
			png->img[offs+5] = 0xff;
			break;
		}
		break;
	case COLOR_PALETTE:
		switch (png->depth) {
		case 1:
			offs = y * png->row + 1 + x;
			png->img[offs] = color & 1;
			break;
		case 2:
			offs = y * png->row + 1 + x;
			png->img[offs] = color & 3;
			break;
		case 4:
			offs = y * png->row + 1 + x;
			png->img[offs] = color & 15;
			break;
		case 8:
			offs = y * png->row + 1 + x;
			png->img[offs] = color;
			break;
		}
	case COLOR_GRAYALPHA:
		switch (png->depth) {
		case 8:
			offs = y * png->row + 1 + x * 2;
			png->img[offs+0] = color;
			png->img[offs+1] = alpha;
			break;
		case 16:
			offs = y * png->row + 1 + x * 4;
			png->img[offs+0] = color >> 8;
			png->img[offs+1] = color;
			png->img[offs+2] = alpha >> 8;
			png->img[offs+3] = alpha;
			break;
		}
		break;
	case COLOR_RGBALPHA:
		switch (png->depth) {
		case 8:
			offs = y * png->row + 1 + x * 4;
			png->img[offs+0] = color >> 16;
			png->img[offs+1] = color >> 8;
			png->img[offs+2] = color;
			png->img[offs+3] = alpha;
			break;
		case 16:
			offs = y * png->row + 1 + x * 8;
			png->img[offs+0] = color >> 16;
			png->img[offs+1] = 0xff;
			png->img[offs+2] = color >> 8;
			png->img[offs+3] = 0xff;
			png->img[offs+4] = color;
			png->img[offs+5] = 0xff;
			png->img[offs+6] = alpha >> 8;
			png->img[offs+7] = alpha;
			break;
		}
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	return 0;
}

int png_get_pixel(png_t *png, int x, int y, int *color, int *alpha)
{
	size_t offs;
	int bkgd;

	if (NULL == png || NULL == png->img || NULL == color || NULL == alpha) {
		errno = EINVAL;
		return -1;
	}
	*color = 0;
	*alpha = 0;

	if (x < 0 || x >= png->w || y < 0 || y >= png->h) {
		return 0;
	}

	switch (png->bkgd_size) {
	case 1:
		bkgd = png->bkgd[0];
		break;
	case 2:
		bkgd = (png->bkgd[0] << 8) | png->bkgd[1];
		break;
	case 3:
		bkgd = (png->bkgd[0] << 16) | (png->bkgd[1] << 8) | png->bkgd[2];
		break;
	case 6:
		bkgd = (png->bkgd[0] << 16) | (png->bkgd[2] << 8) | png->bkgd[4];
		break;
	default:
		bkgd = png->bkgd[0];
	}

	switch (png->color) {
	case COLOR_GRAYSCALE:
		switch (png->depth) {
		case 1:
			offs = y * png->row + 1 + x / 8;
			*color = (png->img[offs] >> (x % 8)) & 1;
			break;
		case 2:
			offs = y * png->row + 1 + x / 4;
			*color = (png->img[offs] >> (x % 4)) & 3;
			break;
		case 4:
			offs = y * png->row + 1 + x / 2;
			*color = (png->img[offs] >> (x % 2)) & 15;
			break;
		case 8:
			offs = y * png->row + 1 + x;
			*color = png->img[offs];
			break;
		case 16:
			offs = y * png->row + 1 + x * 2;
			*color = (png->img[offs+0] << 8) | png->img[offs+1];
			break;
		}
		break;
	case COLOR_RGBTRIPLE:
		switch (png->depth) {
		case 8:
			offs = y * png->row + 1 + x * 3;
			*color =
				(png->img[offs+0] << 16) |
				(png->img[offs+1] <<  8) |
				(png->img[offs+2] <<  0);
			break;
		case 16:
			offs = y * png->row + 1 + x * 6;
			*color =
				(png->img[offs+0] << 16) |
				(png->img[offs+2] <<  8) |
				(png->img[offs+4] <<  0);
			break;
		}
		break;
	case COLOR_PALETTE:
		switch (png->depth) {
		case 1:
			offs = y * png->row + 1 + x;
			*color = png->img[offs] & 1;
			break;
		case 2:
			offs = y * png->row + 1 + x;
			*color = png->img[offs] & 3;
			break;
		case 4:
			offs = y * png->row + 1 + x;
			*color = png->img[offs] & 15;
			break;
		case 8:
			offs = y * png->row + 1 + x;
			*color = png->img[offs];
			break;
		}
	case COLOR_GRAYALPHA:
		switch (png->depth) {
		case 8:
			offs = y * png->row + 1 + x * 2;
			*color = png->img[offs+0];
			*alpha = png->img[offs+1];
			break;
		case 16:
			offs = y * png->row + 1 + x * 4;
			*color = (png->img[offs+0] << 8) | png->img[offs+1];
			*alpha = (png->img[offs+2] << 8) | png->img[offs+3];
			break;
		}
		break;
	case COLOR_RGBALPHA:
		switch (png->depth) {
		case 8:
			offs = y * png->row + 1 + x * 4;
			*color =
				(png->img[offs+0] << 16) |
				(png->img[offs+1] <<  8) |
				(png->img[offs+2] <<  0);
			*alpha = png->img[offs+3];
			break;
		case 16:
			offs = y * png->row + 1 + x * 8;
			*color =
				(png->img[offs+0] << 16) |
				(png->img[offs+2] <<  8) |
				(png->img[offs+4] <<  0);
			*alpha = (png->img[offs+6] << 8) | png->img[offs+7];
			break;
		}
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	if (bkgd == *color) {
		*color = -1;
	}

	return 0;
}

int png_rectangle(png_t *png, int x0, int y0, int x1, int y1,
	int color, int alpha)
{
	int x, y;
	if (NULL == png) {
		errno = EINVAL;
		return -1;
	}
	for (x = x0; x <= x1; x++) {
		png_put_pixel(png, x, y0, color, alpha);
		png_put_pixel(png, x, y1, color, alpha);
	}
	for (y = y0; y <= y1; y++) {
		png_put_pixel(png, x0, y, color, alpha);
		png_put_pixel(png, x1, y, color, alpha);
	}
	return 0;
}

int png_filled_rectangle(png_t *png, int x0, int y0, int x1, int y1,
	int color, int alpha)
{
	int x, y;
	if (NULL == png) {
		errno = EINVAL;
		return -1;
	}
	for (y = y0; y <= y1; y++) {
		for (x = x0; x <= x1; x++) {
			png_put_pixel(png, x, y, color, alpha);
		}
	}
	return 0;
}

int png_tinted_rectangle(png_t *png, int x0, int y0, int x1, int y1,
	int color, int alpha)
{
	int x, y, c, a;
	if (NULL == png) {
		errno = EINVAL;
		return -1;
	}
	for (y = y0; y <= y1; y++) {
		for (x = x0; x <= x1; x++) {
			png_get_pixel(png, x, y, &c, &a);
			c = c & color;
			a = a & alpha;
			png_put_pixel(png, x, y, c, a);
		}
	}
	return 0;
}

int png_printch(png_t *png, int x0, int y0, int color, int alpha, int ch)
{
	uint8_t *font;
	int y;
	FUN("png_printch");

	if (NULL == font8x13) {
		return -1;
	}
	if (ch < 0 || ch > 255) {
		errno = EINVAL;
		return -1;
	}

	font = &font8x13[13 * ch];
	for (y = 0; y < 13; y++, font++) {
		uint8_t byte = *font;
		if (byte & 0x80) png_put_pixel(png, x0 + 0, y0 + y, color, alpha);
		if (byte & 0x40) png_put_pixel(png, x0 + 1, y0 + y, color, alpha);
		if (byte & 0x20) png_put_pixel(png, x0 + 2, y0 + y, color, alpha);
		if (byte & 0x10) png_put_pixel(png, x0 + 3, y0 + y, color, alpha);
		if (byte & 0x08) png_put_pixel(png, x0 + 4, y0 + y, color, alpha);
		if (byte & 0x04) png_put_pixel(png, x0 + 5, y0 + y, color, alpha);
		if (byte & 0x02) png_put_pixel(png, x0 + 6, y0 + y, color, alpha);
		if (byte & 0x01) png_put_pixel(png, x0 + 7, y0 + y, color, alpha);
	}

	return 0;
}

int png_printf(png_t *png, int x, int y, int color, int alpha,
	const char *fmt, ...)
{
	char *buff = NULL, *src;
	int x0 = x;
	size_t len;
	va_list ap;
	FUN("png_printf");

	va_start(ap, fmt);
	len = pm_vasprintf(&buff, fmt, ap);
	va_end(ap);

	/* very simple string print character loop */
	for (src = buff; *src; src++) {
		switch (*src) {
		case '\b':
			x -= 8;
			break;
		case '\r':
			x = x0;
			break;
		case '\n':
			y += 13;
			break;
		default:
			png_printch(png, x, y, color, alpha, *src);
			x += 8;
		}
	}
	xfree(buff);
	return len;
}

#undef	LINESIZE
#define	LINESIZE	64

int png(void)
{
	char *filename = NULL;
	size_t len = strlen(g_conf->configpath) + strlen(FONTNAME) + 1;
	char *line = NULL;
	FILE *fp;
	size_t offs, size;
	FUN("png");

	filename = xcalloc(len, sizeof(char));
	pm_snprintf(filename, len, "%s%s",
		g_conf->configpath, FONTNAME);
	LOG(L_MINOR,("loading font %s\n", filename));
	fp = fopen(filename, "r");
	if (NULL == fp) {
		LOG(L_ERROR,("could not open font %s\n",
			filename));
		info("%s missing ", FONTNAME);
		xfree(filename);
		return -1;
	}
	xfree(filename);

	/* size of the font after decoding */
	size = 256 * 13 * sizeof(uint8_t);
	font8x13 = xmalloc(size);

	line = xcalloc(LINESIZE, sizeof(char));

	offs = 0;
	while (!feof(fp)) {
		uint8_t bits;
		if (NULL == fgets(line, LINESIZE, fp))
			break;
		if (*line == '\n')
			continue;
		if (*line == '#') {
			offs = 13 * strtoul(line + 2, NULL, 0);
			if (offs > size) {
				LOG(L_ERROR,("invalid character number '%s'\n",
					line));
				return -1;
			}
		}
		bits = 0;
		if ('x' == line[0]) bits |= 0x80;
		if ('x' == line[1]) bits |= 0x40;
		if ('x' == line[2]) bits |= 0x20;
		if ('x' == line[3]) bits |= 0x10;
		if ('x' == line[4]) bits |= 0x08;
		if ('x' == line[5]) bits |= 0x04;
		if ('x' == line[6]) bits |= 0x02;
		if ('x' == line[7]) bits |= 0x01;
		font8x13[offs] = bits;
		if (++offs >= size)
			break;
	}
	fclose(fp);
	info("%s ", FONTNAME);

	return 0;
}
